#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
B站视频下载器
从CSV文件读取视频信息并下载视频和音频，合成为完整视频文件
"""

import os
import sys
import subprocess
import pandas as pd
import re
import logging
from pathlib import Path
from typing import List, Dict, Optional, Tuple
from datetime import datetime

# 添加flipcrawler模块到路径
sys.path.append(str(Path(__file__).parent))
from flipcrawler.config import DOWNLOAD_CONFIG, PATH_CONFIG, TASK_CONFIG


class BiliVideoDownloader:
    """B站视频下载器"""
    
    def __init__(self, project_root: Optional[str] = None):
        self.project_root = Path(project_root or PATH_CONFIG["project_root"]).resolve()
        self.data_dir = self.project_root / PATH_CONFIG["data_dir"]
        self.video_output_dir = self.project_root / PATH_CONFIG["video_output_dir"]
        self.log_dir = self.project_root / PATH_CONFIG["log_dir"]
        
        # 创建必要的目录
        self.video_output_dir.mkdir(parents=True, exist_ok=True)
        self.log_dir.mkdir(parents=True, exist_ok=True)
        
        # 设置日志
        self._setup_logging()
        
        # 检查下载工具
        self._check_downloader()
    
    def _setup_logging(self):
        """设置日志配置"""
        log_file = self.log_dir / f"video_download_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
        
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler(log_file, encoding='utf-8'),
                logging.StreamHandler(sys.stdout)
            ]
        )
        self.logger = logging.getLogger(__name__)
    
    def _check_downloader(self):
        """检查下载工具是否可用"""
        downloader = DOWNLOAD_CONFIG["downloader"]
        try:
            result = subprocess.run([downloader, "--version"], 
                                  capture_output=True, text=True, timeout=10)
            if result.returncode == 0:
                self.logger.info(f"下载工具 {downloader} 可用: {result.stdout.strip().split()[0]}")
            else:
                raise FileNotFoundError()
        except (FileNotFoundError, subprocess.TimeoutExpired):
            self.logger.error(f"下载工具 {downloader} 不可用，请先安装")
            self.logger.info(f"安装命令: pip install {downloader}")
            sys.exit(1)
    
    def find_csv_files(self, category: Optional[str] = None) -> List[Path]:
        """查找CSV文件
        
        Args:
            category: 指定类别，如 "动画"。如果为None，返回所有CSV文件
            
        Returns:
            CSV文件路径列表
        """
        csv_files = []
        
        # 搜索data目录下的所有子目录
        for data_subdir in self.data_dir.glob("*/"):
            if data_subdir.is_dir():
                if category:
                    # 查找特定类别的CSV文件
                    pattern = f"*{category}*.csv"
                    csv_files.extend(data_subdir.glob(pattern))
                else:
                    # 查找所有CSV文件
                    csv_files.extend(data_subdir.glob("*.csv"))
        
        return sorted(csv_files)
    
    def read_video_info_from_csv(self, csv_file: Path, limit: Optional[int] = None) -> List[Dict]:
        """从CSV文件读取视频信息
        
        Args:
            csv_file: CSV文件路径
            limit: 限制读取的视频数量
            
        Returns:
            视频信息列表
        """
        try:
            df = pd.read_csv(csv_file)
            
            if limit:
                df = df.head(limit)
            
            videos = []
            for _, row in df.iterrows():
                video_info = {
                    'bvid': row.get('bvid', ''),
                    'title': row.get('视频标题', ''),
                    'url': row.get('视频地址', ''),
                    'author': row.get('作者', ''),
                    'view_count': row.get('播放数', 0),
                    'like_count': row.get('点赞数', 0)
                }
                videos.append(video_info)
            
            self.logger.info(f"从 {csv_file.name} 读取到 {len(videos)} 个视频信息")
            return videos
            
        except Exception as e:
            self.logger.error(f"读取CSV文件失败 {csv_file}: {e}")
            return []
    
    def clean_filename(self, filename: str) -> str:
        """清理文件名中的特殊字符"""
        if not TASK_CONFIG["clean_filename"]:
            return filename
        
        # 移除或替换特殊字符
        filename = re.sub(r'[<>:"/\\|?*]', '_', filename)
        filename = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', filename)  # 移除控制字符
        filename = filename.strip('. ')  # 移除首尾的点和空格
        
        # 限制文件名长度
        max_length = TASK_CONFIG["max_filename_length"]
        if len(filename) > max_length:
            filename = filename[:max_length-3] + "..."
        
        return filename
    
    def generate_output_filename(self, video_info: Dict, index: int = 0) -> str:
        """生成输出文件名
        
        Args:
            video_info: 视频信息字典
            index: 视频索引
            
        Returns:
            清理后的文件名（不含扩展名）
        """
        format_str = TASK_CONFIG["filename_format"]
        
        # 准备格式化变量
        format_vars = {
            'bvid': video_info.get('bvid', 'unknown'),
            'title': video_info.get('title', 'untitled'),
            'author': video_info.get('author', 'unknown'),
            'index': f"{index:03d}"
        }
        
        # 格式化文件名
        try:
            filename = format_str.format(**format_vars)
        except KeyError as e:
            self.logger.warning(f"文件名格式化失败，使用默认格式: {e}")
            filename = f"{format_vars['bvid']}_{format_vars['title']}"
        
        return self.clean_filename(filename)
    
    def download_video(self, video_info: Dict, output_filename: str) -> Tuple[bool, str]:
        """下载单个视频
        
        Args:
            video_info: 视频信息字典
            output_filename: 输出文件名（不含扩展名）
            
        Returns:
            (是否成功, 错误信息)
        """
        url = video_info.get('url', '')
        if not url:
            return False, "视频URL为空"
        
        bvid = video_info.get('bvid', '')
        title = video_info.get('title', 'Unknown')
        
        # 检查文件是否已存在
        output_path = self.video_output_dir / f"{output_filename}.{DOWNLOAD_CONFIG['output_format']}"
        if TASK_CONFIG["skip_existing"] and output_path.exists():
            self.logger.info(f"文件已存在，跳过下载: {output_path.name}")
            return True, "文件已存在"
        
        self.logger.info(f"开始下载视频: {title} ({bvid})")
        
        # 构建下载命令
        cmd = self._build_download_command(url, output_filename)
        
        try:
            # 执行下载
            self.logger.info(f"执行命令: {' '.join(cmd)}")
            
            result = subprocess.run(
                cmd,
                cwd=self.video_output_dir,
                capture_output=True,
                text=True,
                timeout=DOWNLOAD_CONFIG["timeout"]
            )
            
            if result.returncode == 0:
                self.logger.info(f"下载成功: {title} -> {output_path.name}")
                return True, "下载成功"
            else:
                error_msg = result.stderr or result.stdout or "未知错误"
                self.logger.error(f"下载失败: {title} - {error_msg}")
                return False, error_msg
                
        except subprocess.TimeoutExpired:
            error_msg = f"下载超时 ({DOWNLOAD_CONFIG['timeout']}秒)"
            self.logger.error(f"下载超时: {title}")
            return False, error_msg
        except Exception as e:
            error_msg = f"下载异常: {str(e)}"
            self.logger.error(f"下载异常: {title} - {e}")
            return False, error_msg
    
    def _build_download_command(self, url: str, output_filename: str) -> List[str]:
        """构建下载命令
        
        Args:
            url: 视频URL
            output_filename: 输出文件名（不含扩展名）
            
        Returns:
            命令参数列表
        """
        downloader = DOWNLOAD_CONFIG["downloader"]
        cmd = [downloader]
        
        # 基本参数 - B站需要手动指定视频+音频格式
        cmd.extend([
            "--format", "30064+30280/30032+30280/30011+30280/best",  # 720p视频+高质量音频，降级策略
            "--merge-output-format", DOWNLOAD_CONFIG["output_format"],
            "--output", f"{output_filename}.%(ext)s",
            "--no-warnings"  # 减少警告信息
        ])
        
        # 重试次数
        if DOWNLOAD_CONFIG["retry_count"] > 0:
            cmd.extend(["--retries", str(DOWNLOAD_CONFIG["retry_count"])])
        
        # 速度限制
        if DOWNLOAD_CONFIG["rate_limit"]:
            cmd.extend(["--limit-rate", DOWNLOAD_CONFIG["rate_limit"]])
        
        # 字幕下载（B站需要登录，暂时禁用）
        # if DOWNLOAD_CONFIG["download_subtitles"]:
        #     cmd.append("--write-subs")
        #     if DOWNLOAD_CONFIG["subtitle_langs"]:
        #         cmd.extend(["--sub-langs", ",".join(DOWNLOAD_CONFIG["subtitle_langs"])])
        
        # 临时文件处理
        if not DOWNLOAD_CONFIG["keep_temp_files"]:
            cmd.append("--no-keep-fragments")
        
        # 添加URL
        cmd.append(url)
        
        return cmd
    
    def download_from_category(self, category: str, limit: int = 1) -> Dict:
        """从指定类别下载视频
        
        Args:
            category: 视频类别，如 "动画"
            limit: 下载数量限制
            
        Returns:
            下载结果统计
        """
        self.logger.info(f"开始下载 {category} 类别的前 {limit} 个视频")
        
        # 查找CSV文件
        csv_files = self.find_csv_files(category)
        if not csv_files:
            self.logger.error(f"未找到 {category} 类别的CSV文件")
            return {"success": 0, "failed": 0, "skipped": 0, "errors": []}
        
        # 使用最新的CSV文件
        csv_file = csv_files[-1]
        self.logger.info(f"使用CSV文件: {csv_file}")
        
        # 读取视频信息
        videos = self.read_video_info_from_csv(csv_file, limit)
        if not videos:
            self.logger.error("未读取到视频信息")
            return {"success": 0, "failed": 0, "skipped": 0, "errors": []}
        
        # 下载统计
        stats = {"success": 0, "failed": 0, "skipped": 0, "errors": []}
        
        # 逐个下载
        for i, video_info in enumerate(videos, 1):
            try:
                output_filename = self.generate_output_filename(video_info, i)
                success, message = self.download_video(video_info, output_filename)
                
                if success:
                    if "已存在" in message:
                        stats["skipped"] += 1
                    else:
                        stats["success"] += 1
                else:
                    stats["failed"] += 1
                    stats["errors"].append({
                        "video": video_info.get('title', 'Unknown'),
                        "bvid": video_info.get('bvid', ''),
                        "error": message
                    })
                    
            except Exception as e:
                self.logger.error(f"处理视频时出错: {e}")
                stats["failed"] += 1
                stats["errors"].append({
                    "video": video_info.get('title', 'Unknown'),
                    "bvid": video_info.get('bvid', ''),
                    "error": str(e)
                })
        
        # 输出统计结果
        self.logger.info(f"下载完成 - 成功: {stats['success']}, 失败: {stats['failed']}, 跳过: {stats['skipped']}")
        if stats["errors"]:
            self.logger.info("失败详情:")
            for error in stats["errors"]:
                self.logger.info(f"  - {error['video']} ({error['bvid']}): {error['error']}")
        
        return stats


def main():
    """主函数"""
    import argparse
    
    parser = argparse.ArgumentParser(description="B站视频下载器")
    parser.add_argument("--category", "-c", default="动画", help="视频类别 (默认: 动画)")
    parser.add_argument("--limit", "-l", type=int, default=1, help="下载数量限制 (默认: 1)")
    parser.add_argument("--project-root", "-r", help="项目根目录路径")
    
    args = parser.parse_args()
    
    try:
        downloader = BiliVideoDownloader(args.project_root)
        stats = downloader.download_from_category(args.category, args.limit)
        
        print(f"\n=== 下载完成 ===")
        print(f"成功: {stats['success']}")
        print(f"失败: {stats['failed']}")
        print(f"跳过: {stats['skipped']}")
        
        if stats['failed'] > 0:
            sys.exit(1)
            
    except KeyboardInterrupt:
        print("\n用户中断下载")
        sys.exit(1)
    except Exception as e:
        print(f"程序异常: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()